Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote authenticator and authorizer #234

Merged
merged 7 commits into from
Feb 20, 2025
Merged

Conversation

moroten
Copy link
Contributor

@moroten moroten commented Dec 20, 2024

The authenticate and authorize tasks can now be sent remotely over gRPC to an external service. This way, custom authentication and authorization does not require a modified builds of the Buildbarn components.

To avoid spamming the remote service with calls for every REv2 request and keep the latency low, the verdicts, both allow and deny, are cached for a duration specified in the response from the remote service.

@moroten moroten force-pushed the remote-auth branch 3 times, most recently from 8e43646 to 47d3807 Compare December 20, 2024 14:13
@moroten
Copy link
Contributor Author

moroten commented Jan 15, 2025

@EdSchouten Welcome back from Christmas holiday. I forgot to bring this up for discussion yesterday. Any comments? No hurry, take your time.

@moroten
Copy link
Contributor Author

moroten commented Jan 24, 2025

I've now rebased and fixed all the review comments. The changes were done in Add remote auth .proto definitions and Implement remote authenticator and authorizer.

@moroten
Copy link
Contributor Author

moroten commented Jan 29, 2025

@EdSchouten friendly ping. Would you like me to click "Resolve conversation"?

"google.golang.org/grpc/status"
)

type remoteRequestAuthenticator struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on calling this requestHeadersAuthenticator as well? Yes, it would be the same name as the underlying interface, but because it's in a different package (auth.RequestHeaderAuthenticator vs. http.NewRequestHeadersAuthenticator()) they would still be sufficiently distinct.

The reason I'm bringing this up is because there isn't really anything "remote" about this type. It would be perfectly fine to have an implementation of auth.RequestHeadersAuthenticator that looks at stuff in a SQLite database.

This also makes me wonder: should we reimplement our JWT auth to be built on top of this interface as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also makes me wonder: should we reimplement our JWT auth to be built on top of this interface as well?

Is that just to extract a single header into the JWT handler? Maybe I didn't get how you meant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we have these:

-rw-r--r--   1 ed  staff   1240 Dec 15 10:38 pkg/grpc/jwt_authenticator.go
-rw-r--r--   1 ed  staff   1048 Dec 15 10:38 pkg/http/jwt_authenticator.go

But considering that we now have pkg/{grpc,http}/request_header_authenticator.go, we no longer need these, right? We could throw them away and provide a single implementation of auth.RequestHeadersAuthenticator that does the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, I get it. I've pushed an update which I've verified in bb-deployments docker-compose for the gRPC header handling.


message RemoteHttpRequestAuthenticationPolicy {
// The remote authenticator to grant or deny access the HTTP request.
buildbarn.configuration.grpc.RemoteAuthenticationPolicy backend = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this message live in buildbarn.configuration.auth, because it's independent of gRPC/HTTP?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates a circular dependency between grpc.proto and auth.proto: grpc.ServerConfiguration -> grpc.AuthenticationPolicy -> auth.RemoteAuthenticator -> grpc.ClientConfiguration. If we split into grpc_client.proto and grpc_server.proto, it will work. Shall I do that?

// if a proxy has already performed authentication where the resulting HTTP
// request's headers need to be verified and processed in a custom way to
// produce buildbarn.auth.AuthenticationMetadata.
RemoteGrpcRequestAuthenticationPolicy remote = 8;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to replace this field and the jwt with one called request_headers or something, containing a oneof for remote and jwt inside? That way people can add new request header based authenticators without requiring further code changes to HTTP and gRPC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the factory code, it makes sense.

@moroten
Copy link
Contributor Author

moroten commented Feb 18, 2025

@EdSchouten Two open questions at the moment:

  1. Do you prefer keeping or removing the deduplication of requests that haven't yet been cached?
  2. Shall I move RemoteRequestAuthenticationPolicy into auth.proto, in which case grpc.proto needs to be split into grep_client.proto and grpc_server.proto?

Would you like the fixes as fixup-commits or rebased (squashed) into the existing commits?

@moroten
Copy link
Contributor Author

moroten commented Feb 19, 2025

It would be nice to put RequestHeadersAuthenticator and RemoteAuthenticator in auth.proto, but that creates the following dependency cycle between grpc.proto and auth.proto:
grpc.ServerConfiguration -> grpc.AuthenticationPolicy -> auth.RequestHeadersAuthenticator -> auth.RemoteAuthenticator -> grpc.ClientConfiguration. This can be solved by splitting grpc.proto into grpc_client.proto and grpc_server.proto.

Both grpc.ServerConfiguration and grpc.ClientConfiguration use grpc.TracingMethodConfiguration. Where shall that one be moved to grpc_client.proto as well or to an independent grpc_tracing.proto?

It starts to become a bit of a refactoring, but I'm up for doing it if you think this is the way to do it.

Just a detail, shall the name be pkg/proto/configuration/grpc_client/grpc_client.proto with the go package name with underscore? Protobuf suggests with underscore but Go suggests without in package names.

@EdSchouten
Copy link
Member

It would be nice to put RequestHeadersAuthenticator and RemoteAuthenticator in auth.proto, but that creates the following dependency cycle between grpc.proto and auth.proto: grpc.ServerConfiguration -> grpc.AuthenticationPolicy -> auth.RequestHeadersAuthenticator -> auth.RemoteAuthenticator -> grpc.ClientConfiguration. This can be solved by splitting grpc.proto into grpc_client.proto and grpc_server.proto.

Ugh! Well, let's not go that far for now. Let's just accept that there's a minimal amount of overlap between the two then. Can you at least add a TODO/comment/... something to the code to document that this overlap exists?

Copy link
Member

@EdSchouten EdSchouten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for working on this, @moroten. Really appreciated.

I have some final (small) remarks. Be sure to address those, and I'll merge this swiftly.

return response.err
}
// No valid cache entry available. Deduplicate requests.
if responseReady, ok := a.pendingRequests[requestKey]; ok {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit: for readability, I'd appreciate it that we wrote code like this:

if foo {
    A
    // no return here
} else {
   B
   return
}

As this instead:

if !foo {
   B
   return
}
A

This makes it more explicit that the select { case <-responseReady: ... } falls through and causes the loop to run again, as it'll now be closer to the bottom of the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also changed

select {
case <-ctx.Done():
    return ctx.Err()
case <-wakeupChan:
    A
}

into

select {
case <-ctx.Done():
    return ctx.Err()
case <-wakeupChan:
}
A

Prepare for remote authentication and authorization to avoid future
curcular dependency.
The authenticate and authorize tasks can now be sent remotely over gRPC
to an external service. This way, custom authentication and
authorization does not require a modified builds of the Buildbarn
components.

To avoid spamming the remote service with calls for every REv2 request
and keep the latency low, the verdicts, both allow and deny, are cached
for a duration specified in the response from the remote service.
@EdSchouten
Copy link
Member

Perfect! Will merge as soon as CI is happy.

@moroten
Copy link
Contributor Author

moroten commented Feb 20, 2025

It would be nice to put RequestHeadersAuthenticator and RemoteAuthenticator in auth.proto, but that creates the following dependency cycle between grpc.proto and auth.proto: grpc.ServerConfiguration -> grpc.AuthenticationPolicy -> auth.RequestHeadersAuthenticator -> auth.RemoteAuthenticator -> grpc.ClientConfiguration. This can be solved by splitting grpc.proto into grpc_client.proto and grpc_server.proto.

Ugh! Well, let's not go that far for now. Let's just accept that there's a minimal amount of overlap between the two then. Can you at least add a TODO/comment/... something to the code to document that this overlap exists?

I added a comment in pkg/grpc/authenticator.go and pkg/http/authenticator.go to keep the .proto a bit smaller for users reading them as documentation of the configuration.

@EdSchouten EdSchouten merged commit 214cfae into buildbarn:master Feb 20, 2025
1 check passed
@moroten moroten deleted the remote-auth branch February 20, 2025 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants